-- ElementAltarM
-- Created by panyl
-- 元素圣坛

module("ElementAltarM", package.seeall);

local elements = {};
local tree = {};
local altarList = {};
local elementLord = {};

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    -- 转换下element信息表
    local element_CSV = get_element_CSV();
    for i = 1, #element_CSV do
        elements[element_CSV[i].element] = element_CSV[i];
    end

    element_CSV = {};

    -- 转换下element_lord信息表
    local element_lord_CSV = get_element_lord_CSV();
    for i = 1, #element_lord_CSV do
        elementLord[element_lord_CSV[i].element] = element_lord_CSV[i];
    end

    element_lord_CSV = {};

    -- 转换下element信息表
    local element_altar_CSV = get_element_altar_CSV();
    for i = 1, #element_altar_CSV do
        local row = element_altar_CSV[i];
        altarList[row.id] = row;

        local key = bit.blshift(row["first"], 16) +
                    bit.blshift(row["second"], 8) + row["third"];

        tree[key] = row;
    end

    element_altar_CSV = {};
end

function init()
    loadCsv();

    -- 测试用
    EventMgr.removeAll("ElementAltarM");
    EventMgr.register("ElementAltarM", event.DUNGEON_AREADY, function()
        --testSubmit();
    end);
end

-- 检索
function query(id, path)
    local m = altarList[id];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 所有信息
function getAllConfig()
    return altarList;
end

-- 是否激活
function isKeepDiary(id)
    if table.indexOf(getDiaryList(), id) ~= -1 then
        return true;
    end

    return false;
end

-- 判断是否激活了日记的所有记录
function isAllRecordActive()
    local ids = table.keys(altarList);
    for _, id in pairs(ids) do
        if not isKeepDiary(id) then
            return false;
        end
    end
    return true;
end

-- 检索元素信息
function queryElement(element, path)
    local m = elements[element];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 检索特殊圣坛元素领主信息
function queryElementLord(element, path)
    local m = elementLord[element];

    if not m or not path then
        return m;
    end

    return m[path];
end

-- 获取已经提交的元素
function getSubmittedElems(pos)
    local grid = DungeonM.getGridByPos(pos);
    -- 如果是特殊元素领主圣坛，则调用专门的接口
    if grid.type == GRID_TYPE_SPECIAL_ELEMENT_LORD then
        return getSpecialSubmittedElems(pos);
    end

    if  grid.type ~= GRID_TYPE_ELEMENT_ALTAR and
        grid.type ~= GRID_TYPE_ELEMENT_LORD  and
        grid.type ~= GRID_TYPE_SKY_ELE_ALTAR then

        return {};
    end

    if grid.state == GRID_STATE_DISABLE then
        return {};
    end

    local submit_state = grid.submit_state or 0;
    local ret = {};

    repeat
        if (submit_state <= 0) then
            break;
        end

        local element = submit_state % 256;
        submit_state = math.modf(submit_state / 256);

        table.insert(ret, 1, element);
    until submit_state <= 0;

    return ret;
end

-- 消耗
function doCost(cost)
    for key, amount in pairs(cost) do
        if type(key) == "number" then
            ItemM.costAmount(ME.user, key, amount);
        elseif type(key) == "string" then
            ME.user:costAttrib(key, amount);
        end
    end
end

-- 判断能否提交，判断格子状态、消耗是否足够
function checkSubmit(element, pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 如果不是圣坛格子
    if grid.type ~= GRID_TYPE_ELEMENT_ALTAR and
        grid.type ~= GRID_TYPE_ELEMENT_LORD and
        grid.type ~= GRID_TYPE_SKY_ELE_ALTAR then

        trace("ElementAltarM", "格子%d不是元素圣坛。", pos);
        return false;
    end

    -- 检查下格子是否失效
    if grid.state == GRID_STATE_DISABLE then
        trace("ElementAltarM", "格子%d已经失效", pos);
        return false;
    end

    -- 如果格子尚未翻开
    if not grid:isOpened() then
        trace("ElementAltarM", "格子%d尚未翻开。");
        return false;
    end

    local cost = queryElement(element, "require") or {};

    if grid.type == GRID_TYPE_ELEMENT_LORD then
        cost = queryElementLord(element, "require") or {};
    end

    local ok = true;

    for key, value in pairs(cost) do
        if type(key) == "string" then
            ok = ME.user.dbase:query(key, 0) >= value;
        elseif type(key) == "number" then
            ok = ItemM.getAmount(ME.user, key) >= value;
        end

        if not ok then
            trace("ElementAltarM", "消耗不足。");
            return ok;
        end
    end

    return ok;
end

-- 获取新提交的水晶朝向
function getNewElemDirection(pos)
    local dirList = getAllDirection();
    local existedElems = getSubmittedElems(pos);
    local index = #existedElems;
    local direction = dirList[index];
    return direction;
end

-- 某种效果
function doApply(data, pos, element, isFinal)
    for key, info in pairs(data) do
        if key == "bonus" then
            -- 奖励类的
            BonusM.doBonus(info, "element_altar_bonus");

            -- 抛出元素圣坛奖励事件
            EventMgr.fire(event.ELEM_ALTAR_BONUS, { ["pos"] = pos, ["bonus"] = info, ["isFinal"] = isFinal});

        elseif key == "magic" then
            -- 魔法类
            local type = info["target_type"];
            local skillId = info["skill_id"];
            local targets = SkillM.getTargets(type, info["amount"]);
            local round = CombatM.getRound();

            -- 获取新提交的水晶朝向
            local direction = getNewElemDirection(pos);

            if #targets == 0 then
                -- 没有目标
                -- 直接抛出事件（外部需要播放光效）
                EventMgr.fire(event.ELEM_DAMAGE, { ["pos"] = pos, ["elem"] = element, ["skillId"] = skillId,
                    ["direction"] = direction, ["isFinal"] = isFinal});
                return;
            end

            -- 传入攻击者信息
            local assailantInfo = {
                ["grid_type"] = GRID_TYPE_ELEMENT_ALTAR,
                ["skill_id"]  = skillId,
            };

            for _, target in pairs(targets) do
                SkillM.apply(ME.user, target, skillId, round, { assailantInfo = assailantInfo, });

                -- 抛出元素射线伤害事件
                EventMgr.fire(event.ELEM_DAMAGE, { ["pos"] = pos, ["elem"] = element, ["skillId"] = skillId,
                    ["target"] = target, ["direction"] = direction, ["isFinal"] = isFinal });
            end

        elseif key == "summon" then
            -- 召唤类
            local para = table.deepcopy(info);

            -- SLIMEC-5057中去掉 by cheny
            -- 标记需要延迟刷新
            -- para.isDelay = true;

            local class = para.class;
            local summonPos = pos;
            local altarGrid = DungeonM.getGridByPos(pos);

            if altarGrid.type == GRID_TYPE_SKY_ELE_ALTAR then
                trace("ElementAltarM", "GRID_TYPE_SKY_ELE_ALTAR ___ %d", pos);
                summonPos = SummonM.findSummonPos(pos);
            end

            if not summonPos or summonPos < 0 then
                trace("ElementAltarM", "找不到有效的召唤位置。");
                return;
            end

            local grid = DungeonM.transformGrid(class, summonPos, GRID_STATE_COVER, para);

            -- 静默翻开格子，并且指定延迟播放
            -- 这样做的目的是：等播放完圣坛光效后才刷出格子
            -- grid:open(true, true);
            grid:changeState(GRID_STATE_OPEN);

            -- 抛出圣坛召唤事件
            EventMgr.fire(event.ELEM_SUMMON, { ["pos"] = pos, ["elem"] = element,
                ["class"] = class, ["isFinal"] = isFinal, });
        else
            -- 暂时没有其他
        end
    end
end

-- 提交
function submit(element, pos)
    -- 判断能否提交
    if not checkSubmit(element, pos) then
        return false;
    end

    trace("ElementAltarM", "提交元素%d。", element);

    -- 先做消耗
    local cost = queryElement(element, "require") or {};
    doCost(cost);

    local grid = DungeonM.getGridByPos(pos);

    -- 看看是否完成了
    local submitState = grid.submit_state or 0;

    submitState = bit.blshift(submitState, 8) + element;
    grid.submit_state = submitState;

    local info = tree[submitState];

    -- 是否圆满
    local isFinal = iif(not info, false, true);

    -- 获取新提交的水晶朝向
    local direction = getNewElemDirection(pos);

    -- 如果未圆满
    if not isFinal then
        -- 元素射线
        local apply = queryElement(element, "apply");
        doApply(apply, pos, element, isFinal);

        -- 添加操作缓存
        DungeonM.addAction({ ["cmd"] = "submit_element", ["pos"] = pos, ["data"] = element, });

        if DungeonM.isCostRound(grid) then
            -- 抛出一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
        end

        -- 抛出提交事件
        EventMgr.fire(event.SUBMIT_ELEM, {["pos"] = pos, ["elem"] = element,
            ["direction"] = direction, ["isFinal"] = false });

        return true, true;
    end

    -- 圆满完成了
    -- 额外效果
    local apply = info["apply"] or {};
    doApply(apply, pos, element, isFinal);

    -- 添加操作缓存
    DungeonM.addAction({ ["cmd"] = "submit_element", ["pos"] = pos, ["data"] = element, });

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
    end

    -- 记录日志
    local diary = ME.user.element_diary or {};
    if diary[info.id] ~= 1 then
        diary[info.id] = 1;
        ME.user.element_diary = diary;

        -- 更新记录时间
        local keepTime = math.modf(socket.gettime());
        ME.user.dbase:set("last_keep_diary", keepTime);

        -- 抛出日记刷新的事件
        EventMgr.fire(event.ELEM_DIARY_APPEND, { ["id"] = info.id, });
    end

    -- 抛出提交事件
    EventMgr.fire(event.SUBMIT_ELEM, {["pos"] = pos, ["id"] = info.id, ["elem"] = element,
        ["direction"] = direction, ["isFinal"] = true, ["info"] = info, });

    -- 天空副本中的元素祭坛，可以一直提交
    if grid.type == GRID_TYPE_SKY_ELE_ALTAR then
        -- 因为这边已经圆满了，可以重新提交
        grid.submit_state = 0;
    else
        grid:changeState(GRID_STATE_DISABLE);
    end

    return true, true;
end

-- 获取日记列表
function getDiaryList()
    local diary = ME.user.element_diary or {};

    return table.keys(diary);
end

-- 获取所有的水晶朝向
function getAllDirection()
    return  { "left", "right", "mid" };
end

-- 更新日志
function updateDiary(buff)
    if not buff then
        return;
    end

    local diary = {};

    -- 创建所有的日记信息
    local buffer = Buffer.create(buff);
    local offset = 1;
    local size = Buffer.size(buffer);
    while (offset <= size) do
        local flag = Buffer.getu8(buffer, offset);
        if (flag > 0) then
            diary[offset - 1] = flag;
        end

        -- 继续下一个字节
        offset = offset + 1;
    end

    ME.user.element_diary = diary;
end

-- 打包日志数据（verify to gs）
function packElementDiary(filter)
    local diary = ME.user.element_diary or {};
    local ret = {__mapping = 1, };
    for id, _ in pairs(diary) do
        if not filter[id] then
            ret[id] = diary[id];
        end
    end

    return ret;
end

-- 测试接口
function testSubmit()
    local pos;
    for index = 1, GRID_SIZE do
        local grid = DungeonM.getGridByPos(index);
        if grid.type == GRID_TYPE_ELEMENT_ALTAR then
            pos = index;
            break;
        end
    end

    submit(ELEMENT_TYPE_EARTH, pos);
    submit(ELEMENT_TYPE_EARTH, pos);
    submit(ELEMENT_TYPE_WATER, pos);
end

-- 与当前圣坛同一类型的元素结晶才能提交成功
function submitLordEle(element, pos)
    -- 判断能否提交
    if not checkSubmit(element, pos) then
        return false;
    end

    trace("ElementAltarM", "提交元素%d。", element);

    -- 先做消耗
    local cost = queryElementLord(element, "require") or {};
    doCost(cost);

    local grid = DungeonM.getGridByPos(pos);

    -- 是否匹配当前的元素类型
    local altarType = grid.altar_type;
    if element ~= altarType then
        -- 播放碎裂的光效
        doFailApply(pos, element);

        if DungeonM.isCostRound(grid) then
            -- 抛出一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
        end

        -- 添加操作缓存
        DungeonM.addAction({ ["cmd"] = "submit_lord_element", ["pos"] = pos, ["data"] = element, });
        return "smash_element", true;
    end

    -- 看看是否完成了
    local submitState = grid.submit_state or 0;

    submitState = bit.blshift(submitState, 8) + element;
    grid.submit_state = submitState;

    local info = tree[submitState];

    -- 是否圆满
    local isFinal = iif(not info, false, true);

    -- 获取新提交的水晶朝向
    local direction = getNewElemDirection(pos);

    -- 元素射线
    local apply = queryElementLord(element, "apply");
    doApply(apply, pos, element, isFinal);

    -- 添加操作缓存
    DungeonM.addAction({ ["cmd"] = "submit_lord_element", ["pos"] = pos, ["data"] = element, });

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
    end

    -- 抛出提交事件
    EventMgr.fire(event.SUBMIT_LORD_ELEM, {["pos"] = pos, ["elem"] = element,
        ["direction"] = direction, ["isFinal"] = isFinal});

    if isFinal then
        -- -- 转换成元素洞窟(原来的格子设置为disable)
        grid:changeState(GRID_STATE_DISABLE);
        local  unrealId = FormulaM.invoke("GET_RUINS_UNREAL_ID", altarType);
        local para = {["class"] = 264, ["unreal_id"] = unrealId};

        -- SLIMEC-5057中去掉 by cheny
        -- -- 标记需要延迟刷新
        -- para.isDelay = true;

        -- 完成后格子需要装换成元素洞窟
        local class = para.class;

        local grid = DungeonM.transformGrid(class, pos, GRID_STATE_COVER, para);

        -- 静默翻开格子，并且指定延迟播放
        -- 这样做的目的是：等播放完圣坛光效后才刷出格子
        -- grid:open(true, true);
        grid:changeState(GRID_STATE_OPEN);
    end

    return true, true;
end

-- 当前提交元素不符合当前祭坛
-- 提交失败
function doFailApply(pos, element)
     -- 获取新提交的水晶朝向
    local dirList = getAllDirection();
    local existedElems = getSubmittedElems(pos);
    local index = #existedElems;
    local direction = dirList[index + 1];
    print("当前方向：" .. direction);
        -- 没有目标
    -- 直接抛出事件（外部需要播放光效）
    EventMgr.fire(event.ELEM_FAIL_SUBMIT, { ["pos"] = pos, ["elem"] = element,
                ["direction"] = direction ,});
end

-- 获取特殊元素领主圣坛已经提交的道具
function getSpecialSubmittedElems(pos)
    local grid = DungeonM.getGridByPos(pos);

    if grid.state == GRID_STATE_DISABLE then
        return {};
    end

    if type(grid.submit_state) ~= "table" then
        return {};
    end

    return grid.submit_state or {};
end

-- 与当前圣坛同一类型的元素结晶才能提交成功
function submitSpecialLordEle(classId, pos)
    -- 判断能否提交
    if not checkSpecialSubmit(classId, pos) then
        return false;
    end

    trace("ElementAltarM", "提交特殊元素领主圣坛道具%d。", classId);

    -- 先做消耗
    doCost({[classId] = 1});

    local grid = DungeonM.getGridByPos(pos);
    local require = grid.require;
    local element = ELEM_LIST[table.indexOf(require, classId)];

    -- 提交的是否是指定道具，并且学习了指定称号
    local submitItem = grid.submit_item;
    if classId ~= submitItem or
        (type(grid.career_request) == "number" and not CareerM.hasJoined(grid.career_request)) then

        -- 播放碎裂的光效
        doFailApply(pos, element);

        -- 如果配置了提交失败的结果，那么去执行下
        if grid.submit_fail then
            doApply(grid.submit_fail, pos, element, false);
        end

        if DungeonM.isCostRound(grid) then
            -- 抛出一个回合事件
            EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
        end

        -- 添加操作缓存
        DungeonM.addAction({ ["cmd"] = "submit_special_lord_element", ["pos"] = pos, ["data"] = classId, });
        return "smash_element", true;
    end

    -- 看看是否完成了
    local submitState = getSpecialSubmittedElems(pos);
    table.insert(submitState, classId)
    grid.submit_state = submitState;

    -- 是否圆满
    local isFinal = #submitState == 3;

    -- 获取新提交的水晶朝向
    local direction = getNewElemDirection(pos);

    -- 提交的效果
    if isFinal then
        -- 已经提交完毕了，给奖励
        local bonus = grid.bonus;

        doApply({["bonus"] = bonus}, pos, element, isFinal);
    else
        -- 还在提交
        local submit = grid.submit;
        doApply(submit, pos, element, isFinal);
    end

    -- 添加操作缓存
    DungeonM.addAction({ ["cmd"] = "submit_special_lord_element", ["pos"] = pos, ["data"] = classId, });

    if DungeonM.isCostRound(grid) then
        -- 抛出一个回合事件
        EventMgr.fire(event.COMBAT_ROUND, {["pos"] = grid:getPos(), ["isDelay"] = true,});
    end

    -- 抛出提交事件
    EventMgr.fire(event.SUBMIT_LORD_ELEM, {["pos"] = pos, ["elem"] = element,
        ["direction"] = direction, ["isFinal"] = isFinal});

    if isFinal then
        -- -- 转换成元素洞窟(原来的格子设置为disable)
        grid:changeState(GRID_STATE_DISABLE);
    end

    return true, true;
end

-- 判断特殊元素领主圣坛能否提交，判断格子状态、消耗是否足够
function checkSpecialSubmit(classId, pos)
    local grid = DungeonM.getGridByPos(pos);

    -- 如果不是特殊元素领主圣坛格子
    if grid.type ~= GRID_TYPE_SPECIAL_ELEMENT_LORD then

        trace("ElementAltarM", "格子%d不是特殊元素领主圣坛。", pos);
        return false;
    end

    -- 检查下格子是否失效
    if grid.state == GRID_STATE_DISABLE then
        trace("ElementAltarM", "格子%d已经失效", pos);
        return false;
    end

    -- 如果格子尚未翻开
    if not grid:isOpened() then
        trace("ElementAltarM", "格子%d尚未翻开。");
        return false;
    end

    local cost = {[classId] = 1};
    local ok = true;

    for key, value in pairs(cost) do
        if type(key) == "string" then
            ok = ME.user.dbase:query(key, 0) >= value;
        elseif type(key) == "number" then
            ok = ItemM.getAmount(ME.user, key) >= value;
        end

        if not ok then
            trace("ElementAltarM", "消耗不足。");
            return ok;
        end
    end

    return ok;
end